﻿// The implementation of these functions are inspired from the PSHyperv powershell script
// available at http://www.codeplex.com/PSHyperv

module internal HyperF.WMIInterface

open System
open System.Management



(* ------------------------------------------------ *)
(*   Some utility functions used into this source   *)


/// <summary>
/// Gets an object collection that contains the objects finded via WMI by the specified query.
/// The collection can be empty if the specified query doesn't find any results.
/// </summary>
let private get_WmiObjects (managementPath:string) (query:string) =
  let scope = Utility.getManagementScope managementPath
  use searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query))
  searcher.Get()

/// <summary>
/// Gets the requested object via WMI.
/// If the specified query doesn't find any results, it returns null.
/// If the specified query finds more than one result, it returns only the first.
/// </summary>
let private get_WmiObject_or_null managementPath query =
  use objects = get_WmiObjects managementPath query
  use en = objects.GetEnumerator()
  if en.MoveNext() then en.Current :?> ManagementObject
  else null

/// <summary>
/// Gets the requested object via WMI.
/// If the specified query doesn't find any results, an exception is thrown.
/// If the specified query finds more than one result, it returns only the first.
/// </summary>
let private get_WmiObject managementPath query =
  let ret = get_WmiObject_or_null managementPath query
  if ret = null then failwith "WMI Object not found"
  else ret      

/// <summary>
/// Inspired from Array.filter function, moc_filter returns an array which contains only
/// the elements that satisfy the given function
/// </summary>
let private moc_filter f (coll:ManagementObjectCollection) =
  [| for el in coll do
       let el = el :?> ManagementObject
       if f el then yield el |]

/// <summary>
/// Converts a ManagementObjectCollection into an Array
/// </summary>
let private moc_toArray (coll:ManagementObjectCollection) =
  [| for el in coll -> (el :?> ManagementObject) |]


/// <summary>
/// Builds the management path for the given server (None to indicate local computer)
/// </summary>
let private getManagementPath localPath server =
  match server with
  | None -> localPath
  | Some s -> sprintf @"\\%s\%s" s localPath

let private getVirtualizationManagementPath server =
  getManagementPath @"root\virtualization" server

  
(* --------------------------------------------------------- *)
(* # Functions for Managing Virtual Hard disk (VHD) files  # *)


/// <summary>
/// Gets the default path for the VHD images of the specified HyperV server
/// </summary>
/// <param name="server">The HyperV hostname. None to indicate local computer</param>
let get_VhdDefaultPath server =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  let virtualSystemService = Utility.getServiceObject scope "MsVM_VirtualSystemManagementServiceSettingData"
  virtualSystemService.["DefaultVirtualHardDiskPath"] :?> string
  

/// <summary>
/// Creates a new floppy disk in the specified HyperV server
///
/// #Example: new_VFD None ((get_VhdDefaultPath None) + @"\Floppy.VFD")
/// #         // Creates a new floppy disk in the default folder. 
/// </summary>
let new_VFD server (fullVfdPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "MsVM_ImageManagementService"
  use inParams = imageService.GetMethodParameters("CreateVirtualFloppyDisk")
  inParams.["Path"] <- fullVfdPath
  
  // http://msdn.microsoft.com/en-us/library/cc136784(VS.85).aspx
  use outParams = imageService.InvokeMethod("CreateVirtualFloppyDisk", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to create the floppy disk. Error %d" code
  

/// <summary>
/// Creates a differencing virtual hard disk, based with a parent that already exists
///
/// #Example: let path = ((get_VhdDefaultPath None) + @"\tenby.vhd")
///           new_VHD_Dynamic None path 20
/// #         // Creates a 20GB Dynamic VHD named "tenby.vhd" in the default VHD folder
/// </summary>
let new_VHD_Differencing server (path:string) (parentPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("CreateDifferencingVirtualHardDisk")
  inParams.["ParentPath"] <- parentPath
  inParams.["Path"] <- path
  
  // http://msdn.microsoft.com/en-us/library/cc136776(VS.85).aspx
  use outParams = imageService.InvokeMethod("CreateDifferencingVirtualHardDisk", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to create the virtual hard disk. Error %d" code


/// <summary>
/// Creates a new virtual hard disk, dinamically expanded, in the specified HyperV server, path and with the specified size
///
/// #Example: let path = ((get_VhdDefaultPath None) + @"\tenby.vhd")
///           new_VHD_Dynamic None path 20
/// #         // Creates a 20GB Dynamic VHD named "tenby.vhd" in the default VHD folder
/// </summary>
let new_VHD_Dynamic server (path:string) (maxInternalSize:uint64) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("CreateDynamicVirtualHardDisk")
  inParams.["Path"] <- path
  inParams.["MaxInternalSize"] <- maxInternalSize
  
  // http://msdn.microsoft.com/en-us/library/cc136778(VS.85).aspx
  use outParams = imageService.InvokeMethod("CreateDynamicVirtualHardDisk", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to create the virtual hard disk. Error %d" code


/// <summary>
/// Creates a new virtual fixed-size hard disk in the specified HyperV server, path and with the specified size
///
/// #Example: new_VHD_Fixed None ((get_VhdDefaultPath None) + @"\tenby.vhd") 20
/// #         // Creates a 20GB Fixed VHD named "tenby.vhd" in the default VHD folder
/// </summary>
let new_VHD_Fixed server (path:string) (maxInternalSize:uint64) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("CreateFixedVirtualHardDisk")
  inParams.["Path"] <- path
  inParams.["MaxInternalSize"] <- maxInternalSize
  
  // http://msdn.microsoft.com/en-us/library/cc136780(vs.85).aspx
  use outParams = imageService.InvokeMethod("CreateFixedVirtualHardDisk", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to create the virtual hard disk. Error %d" code


let merge_VHD server (sourcePath:string) (destinationPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("MergeVirtualHardDisk")
  inParams.["SourcePath"] <- sourcePath
  inParams.["DestinationPath"] <- destinationPath
  
  // http://msdn.microsoft.com/en-us/library/cc136776(VS.85).aspx
  use outParams = imageService.InvokeMethod("MergeVirtualHardDisk", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to merge the virtual hard disk. Error %d" code



let get_MountedStorageImage server (vhdPath:string) =
  let param = vhdPath.Replace("\\", "\\\\")
  let query = sprintf "Select * from Msvm_MountedStorageImage where name='%s'" param
  get_WmiObject (getVirtualizationManagementPath server) query

let get_VirtualDiskSCSIDiskDevice server (vhdPath:string) =
  let obj = get_MountedStorageImage server vhdPath
  let param1 = string obj.["TargetId"]
  let param2 = string obj.["Lun"]
  let param3 = string obj.["PortNumber"]
  let query = sprintf "Select * From win32_diskdrive Where Model='Msft Virtual Disk SCSI Disk Device' and ScsiTargetID=%s and ScsiLogicalUnit=%s and scsiPort=%s" param1 param2 param3
  get_WmiObject (getManagementPath @"root\CIMV2" server) query

let get_DiskPartitions server (vhdPath:string) =
  let obj = get_VirtualDiskSCSIDiskDevice server vhdPath
  let param = string obj.["Index"]
  let query = sprintf "Select * From Win32_logicaldisktoPartition Where __PATH Like '%%disk #%s%%'" param
  get_WmiObjects (getManagementPath @"root\CIMV2" server) query |> moc_toArray
  


let mount_VHD server (vhdPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("Mount")
  inParams.["Path"] <- vhdPath

  // http://msdn.microsoft.com/en-us/library/cc136811(VS.85).aspx
  use outParams = imageService.InvokeMethod("Mount", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to mount the specified vhd. Error %d" code



let unmount_VHD server (vhdPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("Unmount")
  inParams.["Path"] <- vhdPath

  // http://msdn.microsoft.com/en-us/library/cc136982(VS.85).aspx
  use outParams = imageService.InvokeMethod("Unmount", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Unable to unmount the specified vhd. Error %d" code
    

// let compact_VHD



/// <summary>
/// #Example: (get_VHDInfo None @"C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Core.vhd").["ParentPath"]
/// #         // Returns the parent path of core.vhd e.g. "C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Brand-new-core.vhd"
/// </summary>
let get_VHDInfo server (vhdPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("GetVirtualHardDiskInfo")
  inParams.["Path"] <- vhdPath

  // http://msdn.microsoft.com/en-us/library/cc136797(VS.85).aspx
  use outParams = imageService.InvokeMethod("GetVirtualHardDiskInfo", inParams, null)
  if outParams = null then failwith "WMI failed"
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    let diskInfo = outParams.["Info"] :?> string
    let doc = new System.Xml.XmlDocument()
    doc.LoadXml(diskInfo)
    doc
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    let diskInfo = outParams.["Info"] :?> string
    let doc = new System.Xml.XmlDocument()
    doc.LoadXml(diskInfo)
    doc
  else
    failwithf "GetVirtualHardDiskInfo failed. Error %d" code



/// <summary>
/// #Exmaple: test_VHD (Some "REALNODE01") @"C:\Users\Public\Documents\Microsoft Hyper-V\Virtual Hard Disks\Core.vhd"
/// #         // Checks the specified VHD disk in the REALNODE01 server
/// </summary>
// Never tested!!!
let test_VHD server (vhdPath:string) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use imageService = Utility.getServiceObject scope "Msvm_ImageManagementService"
  use inParams = imageService.GetMethodParameters("ValidateVirtualHardDisk")
  inParams.["Path"] <- vhdPath
  
  // http://msdn.microsoft.com/en-us/library/cc136983(VS.85).aspx
  use outParams = imageService.InvokeMethod("ValidateVirtualHardDisk", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwith "Unable to test the virtual hard disk"


// let expand_VHD


// let convert_VHD
  
  

(* ---------------------------------------------------- *)
(* # Functions for managing VM information / status   # *)


// TODO: add an XmlDoc
let get_VMHost (domainName:string) =
  let t = new System.DirectoryServices.DirectorySearcher(domainName)
  t.Filter <- "(&(cn=Microsoft hyper-v)(objectCategory=serviceConnectionPoint))"
  t.FindAll()


(*
AssignedNumaNodeList = System.UInt16[]
Caption = Virtual Machine
CreationClassName = Msvm_ComputerSystem
Dedicated = null
Description = Microsoft Virtual Machine
ElementName = Prova
EnabledDefault = 2
EnabledState = 3
HealthState = 5
IdentifyingDescriptions = null
InstallDate = 20090403134612.000000-000
Name = 8A075AAA-E7B8-4798-8894-8C119540BB1C
NameFormat = null
OnTimeInMilliseconds = 0
OperationalStatus = System.UInt16[]
OtherDedicatedDescriptions = null
OtherEnabledState = null
OtherIdentifyingInfo = null
PowerManagementCapabilities = null
PrimaryOwnerContact = null
PrimaryOwnerName = FERRARI\Administrator
ProcessID = 0
RequestedState = 12
ResetCapability = 1
Roles = null
Status = null
StatusDescriptions = null
TimeOfLastConfigurationChange = 20090403134612.000000-000
TimeOfLastStateChange = 20090403150408.000000-000
*)
//
/// <summary>
/// #Example: get_VMByName None "Virtual Computation Node 01"
/// #	      // Returns a single WMI MsVM_ComputerSystem object for the VM named "Virtual Computation Node 01" of the local HyperV server
/// </summary>
let get_VM server (guid:System.Guid) =
  let query = sprintf "select * from Msvm_ComputerSystem Where Name='%s'" (guid.ToString())
  get_WmiObject (getVirtualizationManagementPath server) query


//
/// <summary>
/// #Example: get_VMByName None "Virtual Computation Node 01"
/// #	      // Returns a single WMI MsVM_ComputerSystem object for the VM named "Virtual Computation Node 01" of the local HyperV server
/// </summary>
let get_VMByName server vmName =
  let query = sprintf "select * from Msvm_ComputerSystem Where ElementName='%s'" vmName
  get_WmiObject (getVirtualizationManagementPath server) query


/// <summary>
/// #Example: get_AllVM (Some "REALNODE02")
/// #	      // Returns all the VM present in the REALNODE02 HyperV server
/// #Example: get_AllVM (Some "REALNODE02") |> Array.iter (fun x -> printfn "%s" (x.["ElementName"])
/// #	      // Prints the name of all the VM present in the REALNODE02 HyperV server
/// </summary>
let get_AllVM server =
  let r = get_WmiObjects (getVirtualizationManagementPath server) "select * from Msvm_ComputerSystem"
  r |> moc_filter (fun x -> (string x.["Description"]) = "Microsoft Virtual Machine")
  
  

// TODO: add an XmlDoc
let get_VMKvpExchangeComponent (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm.["Name"]
  let query = sprintf "select * from Msvm_KvpExchangeComponent where systemName='%s'" param
  get_WmiObject (getVirtualizationManagementPath server) query


// TODO: add an XmlDoc
let get_AllVMSummary server (requestedInformation:int array) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("GetSummaryInformation")
  inParams.["RequestedInformation"] <- requestedInformation
  
  // http://msdn.microsoft.com/en-us/library/cc160706(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("GetSummaryInformation", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code <> uint32(Utility.ReturnCode.Completed) then
    failwithf "Failed to retrieve virtual system summary information. Error %d" code
  else
    if outParams.["SummaryInformation"] = null then [||]
    else outParams.["SummaryInformation"] :?> ManagementBaseObject array



// with [|0;1;2;3;4|]
(* 
AsynchronousTasks = null
CreationTime = 20090403134612.134714-000
ElementName = Prova
EnabledState = null
GuestOperatingSystem = null
HealthState = null
Heartbeat = null
MemoryUsage = null
Name = 8A075AAA-E7B8-4798-8894-8C119540BB1C
Notes = 
NumberOfProcessors = 1
ProcessorLoad = null
ProcessorLoadHistory = null
Snapshots = null
ThumbnailImage = null
UpTime = null
*)
let get_VMSummary server vmName (requestedInformation:int array) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope
  let settingData = Utility.getVirtualSystemSetting vm

  use inParams = virtualSystemService.GetMethodParameters("GetSummaryInformation")
  inParams.["RequestedInformation"] <- requestedInformation
  inParams.["SettingData"] <- [| settingData.Path.Path |]
  
  // http://msdn.microsoft.com/en-us/library/cc160706(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("GetSummaryInformation", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code <> uint32(Utility.ReturnCode.Completed) then
    failwithf "Failed to retrieve virtual system summary information. Error %d" code
  else
    if outParams.["SummaryInformation"] = null then null
    else
      let arr = outParams.["SummaryInformation"] :?> ManagementBaseObject array
      arr.[0]



let test (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm.["Name"]
  let query = sprintf "SELECT * From Msvm_VideoHead Where SystemName='%s'" param
  get_WmiObjects (getVirtualizationManagementPath server) query


let get_VMEnabledVideo (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm.["Name"]
  let query = sprintf "SELECT * From Msvm_VideoHead Where SystemName='%s' AND EnabledState=2" param
  let obj1 = get_WmiObject (getVirtualizationManagementPath server) query
  let query = sprintf "SELECT * From Msvm_S3DisplayController Where SystemName='%s'" param
  let obj2 = get_WmiObject (getVirtualizationManagementPath server) query
  (obj1, obj2)



/// <summary>
/// Gets the current screen image of the specified Virtual Machine and 
/// returns it as a thumbnail.
/// This method returns the image in raw RGB 565 format.
/// </summary>
let get_VMThumbnailImage (vm:ManagementObject) (width:uint32) (height:uint32) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  let settingData = Utility.getVirtualSystemSetting vm

  let inParams = virtualSystemService.GetMethodParameters("GetVirtualSystemThumbnailImage")
  inParams.["WidthPixels"] <- string width
  inParams.["HeightPixels"] <- string height
  inParams.["TargetSystem"] <- settingData.Path.Path

  // http://msdn.microsoft.com/en-us/library/cc160707(VS.85).aspx
  let outParams = virtualSystemService.InvokeMethod("GetVirtualSystemThumbnailImage", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    outParams.["ImageData"] :?> byte array
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    outParams.["ImageData"] :?> byte array
  else
    failwithf "Failed retrieving the thumbnail. Error %d" code




let private set_VMState (vm:ManagementObject) (newState:VMState) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use inParams = vm.GetMethodParameters("RequestStateChange")
  inParams.["RequestedState"] <- int(newState)
  
  // http://msdn.microsoft.com/en-us/library/cc723874(VS.85).aspx
  use outParams = vm.InvokeMethod("RequestStateChange", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to change virtual system state. Error %d" code



/// <summary>
/// Starts the specified Virtual Machine
///
/// #Example: start_VM None "Windows XP sp3"
/// #         // Starts the "Windows XP sp3" VM on the local HyperV server
/// </summary>
let start_VM vm = set_VMState vm (VMState.Running)

/// <summary>
/// Saves the current state and suspends the specified Virtual Machine
/// </summary>
let suspend_VM vm = set_VMState vm (VMState.Suspended)

/// <summary>
/// Turns off the specified Virtual Machine
/// </summary>
let stop_VM vm = set_VMState vm (VMState.Stopped)

/// <summary>
/// Pause the specified Virtual Machine
/// </summary>
let pause_VM vm = set_VMState vm (VMState.Paused)

/// <summary>
/// Hard reset the specified Virtual Machine
/// </summary>
let hardReset_VM vm = set_VMState vm (VMState.Reboot)


/// <summary>
/// Shutdowns (sending the command to the OS) the specified Virtual Machine
/// </summary>
let shutdown_VM (vm:ManagementObject) (force:bool) (reason:string) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use inParams = vm.GetMethodParameters("RequestStateChange")
  inParams.["Force"] <- force
  inParams.["Reason"] <- reason
  
  // http://msdn.microsoft.com/en-us/library/cc703625(VS.85).aspx
  use outParams = vm.InvokeMethod("InitiateShutdown", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to shutdown the specified computer. Error %d" code



(* -------------------------------------------------------------------------------------- *)
(* # Functions for working with VMs and their motherboard settings (inc CPU and Memory) # *)


(*
AutoActivate = null
BaseBoardSerialNumber = 9682-3790-0213-1716-9152-5896-01
BIOSGUID = {972DF572-C32D-4A3B-96FE-5D8F204D9712}
BIOSNumLock = False
BIOSSerialNumber = 9682-3790-0213-1716-9152-5896-01
BootOrder = System.UInt16[]
Caption = Virtual Machine
ChassisAssetTag = 6006-3819-5700-6521-8991-1566-50
ChassisSerialNumber = 9682-3790-0213-1716-9152-5896-01
CreationTime = 20090403134612.134714-000
Description = Active settings for the virtual machine.
ElementName = Prova
InstanceID = Microsoft:8A075AAA-E7B8-4798-8894-8C119540BB1C
Notes = 
NumaNodeList = System.UInt16[]
NumaNodesAreRequired = False
OtherVirtualSystemType = null
Parent = null
SettingType = 3
SystemName = 8A075AAA-E7B8-4798-8894-8C119540BB1C
VirtualSystemType = 301
*)
let private get_VMSettingData (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm
  let query = sprintf "ASSOCIATORS OF {%s} Where ResultClass = MsVM_VirtualSystemSettingData" param
  get_WmiObject (getVirtualizationManagementPath server) query



/// <summary>
/// Creates a new virtual machine with the default settings:
/// One single-core processor, 512Mb of RAM, no Hard disk, no NIC, no other stuff
///
/// #Example: new_VM (Some "REALNODE03") "Virtual Compute Node 01" null
/// #         // Creates a new VM called "Virtual Compute Node 01" in REALNODE03 HyperV server
/// #Example: new_VM (Some "REALNODE03") "Virtual Compute Node 01" @"\\REALNODE08\VirtualMachines"
/// #         // Same as above, but it stores the new VM in the specified path
/// </summary>
let new_VM server (newVmName:string) (path:string) =
  let getVirtualSystemGlobalSettingDataInstance (scope:ManagementScope) =
    let settingPath = new ManagementPath("Msvm_VirtualSystemGlobalsettingData")
    use globalSettingClass = new ManagementClass(scope, settingPath, null)
    use globalSettingData = globalSettingClass.CreateInstance()
    globalSettingData.["ElementName"] <- newVmName
    if path <> null then globalSettingData.["ExternalDataRoot"] <- path
    globalSettingData.GetText(TextFormat.WmiDtd20)

  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("DefineVirtualSystem")
  inParams.["ResourcesettingData"] <- null
  inParams.["Sourcesetting"] <- null
  inParams.["SystemsettingData"] <- getVirtualSystemGlobalSettingDataInstance scope
  
  // http://msdn.microsoft.com/en-us/library/cc136786(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("DefineVirtualSystem", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(string outParams.["DefinedSystem"])
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(string outParams.["DefinedSystem"])
  else
    failwithf "Define virtual system failed with error %d" code


let private set_VM_ModifyVirtualSystem (vm:ManagementObject) (settingName:string) (newValue:obj) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("ModifyVirtualSystem")
  inParams.["ComputerSystem"] <- vm.Path.Path
  let settingData = Utility.getVirtualSystemSetting vm

  settingData.[settingName] <- newValue
  inParams.["SystemsettingData"] <- settingData.GetText(TextFormat.WmiDtd20)

  // http://msdn.microsoft.com/en-us/library/cc136809(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ModifyVirtualSystem", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to modify VM. Error %d" code


/// <summary>
/// Changes the name for an existing virtual machine
///
/// #Example: set_VM_Name None "Windows XP sp2" "Windows XP sp3"
/// #         // Changes the name for the VM "Windows XP sp2" with the new name "Windows XP sp3"
/// </summary>
let change_VM_Name vm (newVmName:string) =
  set_VM_ModifyVirtualSystem vm "ElementName" newVmName
  
/// Sets the VM Notes attribute
let change_VM_Notes vm (newNotes:string) = 
  set_VM_ModifyVirtualSystem vm "notes" newNotes


/// <summary>
/// untested function -- TODO: test it
/// Change the VM boot order
/// </summary>
let set_VM_BootOrder vm (newBootOrder:BootMedia array) =
  let bo = newBootOrder |> Array.map (fun x -> int(x))
  set_VM_ModifyVirtualSystem vm "BootOrder" bo

(*  
/// <summary>
/// untested function -- TODO: test it
/// </summary>
let set_VM_AutomaticRecoveryAction vm (newAutomaticRecoveryAction:int) =
  set_VM_ModifyVirtualSystem vm "BootOrder" newAutomaticRecoveryAction

/// <summary>
/// untested function -- TODO: test it
/// </summary>
let set_VM_AutomaticShutdownAction vm (newAutomaticShutdownAction:int) =
  set_VM_ModifyVirtualSystem vm "BootOrder" newAutomaticShutdownAction

/// <summary>
/// untested function -- TODO: test it
/// </summary>
let set_VM_AutomaticStartupAction vm (newAutomaticStartupAction:int) =
  set_VM_ModifyVirtualSystem vm "BootOrder" newAutomaticStartupAction
*)


/// <summary>
/// Removes the specified Virtual machine 
/// NOTE: This function does not delete the hard disks associated
/// </summary>
let remove_VM (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("DestroyVirtualSystem")
  inParams.["ComputerSystem"] <- vm.Path.Path

  // http://msdn.microsoft.com/en-us/library/cc136790(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("DestroyVirtualSystem", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to delete the Virtual Machine. Error %d" code


let private set_VM_ModifyVirtualSystemResources (vm:ManagementObject) (newResource:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  let resources = [| newResource.GetText(TextFormat.WmiDtd20) |]
  use inParams = virtualSystemService.GetMethodParameters("ModifyVirtualSystemResources")
  inParams.["ComputerSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- resources

  // http://msdn.microsoft.com/en-us/library/cc136807(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ModifyVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Modify memory failed. Error: %d" code



(*
Address = null
AllocationUnits = MB
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Memory
Connection = null
ConsumerVisibility = null
Description = Settings for Microsoft Virtual Machine Memory.
DeviceID = null
DeviceIDFormat = null
ElementName = Memory
HostResource = null
InstanceID = Microsoft:8A075AAA-E7B8-4798-8894-8C119540BB1C\4764334d-e001-4176-82ee-5594ec9b530e
IsVirtualized = True
Limit = 512
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = 15129609-B465-4916-AA13-0CF4B109ADB0
Reservation = 512
ResourceSubType = Microsoft Virtual Machine Memory
ResourceType = 4
VirtualQuantity = 512
Weight = 0
*)
/// <summary>
/// Gets the Management Object that represent the Virtual Machine Memory Setting for the specified VM
/// </summary>
let get_VMMemory vm =
  let vmsd = get_VMSettingData vm
  let server = Some( string vm.["__server"] )
  let param = string vmsd
  let query = sprintf "associators of {%s} where resultclass=Msvm_MemorySettingData" param
  get_WmiObject (getVirtualizationManagementPath server) query
  

/// <summary>
/// Sets the new memory amount setting for the specified VM
/// </summary>
/// <param name="server">The HyperV hostname. None to indicate local computer</param>
/// <param name="vmName">The name of the virtual machine</param>
/// <param name="newMemoryAmount">The new memory amount in MegaBytes</param>
let set_VMMemory vm (newMemoryAmount:int64) =
  use memSettingData = get_VMMemory vm
  memSettingData.["AllocationUnits"] <- "MB"
  memSettingData.["Limit"] <- newMemoryAmount
  memSettingData.["Reservation"] <- newMemoryAmount
  memSettingData.["VirtualQuantity"] <- newMemoryAmount
  set_VM_ModifyVirtualSystemResources vm memSettingData


(*
Address = null
AllocationUnits = Processor Cores
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Processor
Connection = null
ConsumerVisibility = null
Description = Settings for Microsoft Virtual Processor.
DeviceID = null
DeviceIDFormat = null
ElementName = Processor
HostResource = null
InstanceID = Microsoft:8A075AAA-E7B8-4798-8894-8C119540BB1C\b637f346-6a0e-4dec-af52-bd70cb80a21d\0
IsVirtualized = True
Limit = 100000
LimitCPUID = False
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = A4F3C4E4-5E15-4018-A713-96C2CFB4C9B8
ProcessorsPerSocket = 2
Reservation = 0
ResourceSubType = Microsoft Processor
ResourceType = 3
SocketCount = 1
ThreadsEnabled = null
VirtualQuantity = 1
Weight = 100
*)
/// <summary>
/// Gets the Management Object that represent the Virtual Machine CPU Setting for the specified VM
/// </summary>
let get_VMCPUCount vm =
  let vmsd = get_VMSettingData vm
  let server = Some( string vm.["__server"] )
  let param = string vmsd
  let query = sprintf "associators of {%s} where resultclass=MsVM_ProcessorSettingData" param
  get_WmiObject (getVirtualizationManagementPath server) query


/// <summary>
/// Sets the new cpu amount setting for the specified VM
/// TODO: sets the number of single-core processors or the number of core for 1 processor?
/// </summary>
/// <param name="server">The HyperV hostname. None to indicate local computer</param>
/// <param name="vmName">The name of the virtual machine</param>
/// <param name="newCpuCount">The new number of CPU</param>
let set_VMCPUCount vm (newCpuCount:int) =
  use memSettingData = get_VMCPUCount vm
  memSettingData.["VirtualQuantity"] <- newCpuCount
  set_VM_ModifyVirtualSystemResources vm memSettingData


(*
let get_VMProcessor (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm
  let query = sprintf "associators of {%s} where resultclass=MSVM_Processor" param
  get_WmiObject (getVirtualizationManagementPath server) query
*)  


(* ---------------------------------------------------------------------------- *)
(* # Functions for working with disk objects , SCSI Controller, Driver, Disk  # *)


(* new_VMRasd 6 "Microsoft Synthetic SCSI Controller" :
*
Address = null
AllocationUnits = Controllers
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = SCSI Controller
Connection = null
ConsumerVisibility = 3
Description = Settings for the Microsoft Synthetic SCSI Controller.
ElementName = SCSI Controller
HostResource = null
InstanceID = Microsoft:Definition\BDE5D4D6-E450-46D2-B925-976CA3E989B4\Default
Limit = 1
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = Microsoft:bde5d4d6-e450-46d2-b925-976ca3e989b4\Root
Reservation = 1
ResourceSubType = Microsoft Synthetic SCSI Controller
ResourceType = 6
VirtualQuantity = 1
Weight = 0
*)
// RASD = Resource Allocation Setting Data
let private new_VMRasd server resType resSubType =
  let query = sprintf "Select * From MsVM_AllocationCapabilities Where ResourceType=%d AND ResourceSubType='%s'" resType resSubType
  let r = get_WmiObject (getVirtualizationManagementPath server) query
  let allocCapsPath = r.["__Path"].ToString().Replace(@"\",@"\\")

  let query = sprintf "Select * From MsVM_SettingsDefineCapabilities Where  valuerange=0 and Groupcomponent = '%s'" allocCapsPath
  let r = get_WmiObject (getVirtualizationManagementPath server) query
  Utility.setManagementObjectAuth( new ManagementObject(string r.["PartComponent"]) )
  
(* array of
*
Address = 0
AllocationUnits = Controllers
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = IDE Controller 0
Connection = null
ConsumerVisibility = 3
Description = Settings for the Microsoft Virtual IDE Controller.
ElementName = IDE Controller 0
HostResource = null
InstanceID = Microsoft:8A075AAA-E7B8-4798-8894-8C119540BB1C\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\0
Limit = 2
MappingBehavior = null
OtherResourceType = null
Parent = null
PoolID = null
Reservation = 1
ResourceSubType = Microsoft Emulated IDE Controller
ResourceType = 5
VirtualQuantity = 1
Weight = 0
*)
let get_VMDiskControllers vm =
  let vmsd = get_VMSettingData vm
  let server = Some( string vm.["__server"] )
  let param = string vmsd
  let query = sprintf "ASSOCIATORS OF {%s} where resultclass= Msvm_ResourceAllocationSettingData" param
  let r = get_WmiObjects (getVirtualizationManagementPath server) query
  r |> moc_filter (fun x -> x.["ResourceSubType"] <> null && ((string x.["ResourceSubType"]) = "Microsoft Emulated IDE Controller" || (string x.["ResourceSubType"]) = "Microsoft Synthetic SCSI Controller")) 


// TODO: change this function to return the GUID instead of name
let get_VMNameByDiskController (controller:ManagementObject) =
  let server = Some( string controller.["__server"] )
  let param = controller.["InstanceID"].ToString().Split([|'\\'|]).[0].Split([|':'|]).[1]
  let query = sprintf "Select elementName from msvm_computersystem where name='%s'" param
  let r = get_WmiObject_or_null (getVirtualizationManagementPath server) query
  if r = null then null
  else string r.["ElementName"]



let get_VMDiskIDEControllers vm =
  let vmsd = get_VMSettingData vm
  let server = Some( string vm.["__server"] )
  let param = string vmsd
  let query = sprintf "ASSOCIATORS OF {%s} where resultclass= Msvm_ResourceAllocationSettingData" param
  let r = get_WmiObjects (getVirtualizationManagementPath server) query
  r |> moc_filter (fun x -> x.["ResourceSubType"] <> null && (string x.["ResourceSubType"]) = "Microsoft Emulated IDE Controller")


let get_VMDiskSCSIControllers vm =
  let vmsd = get_VMSettingData vm
  let server = Some( string vm.["__server"] )
  let param = string vmsd
  let query = sprintf "ASSOCIATORS OF {%s} where resultclass= Msvm_ResourceAllocationSettingData" param
  let r = get_WmiObjects (getVirtualizationManagementPath server) query
  r |> moc_filter (fun x -> x.["ResourceSubType"] <> null && (string x.["ResourceSubType"]) = "Microsoft Synthetic SCSI Controller")
  
  
let get_VMDrivesByController (controller:ManagementObject) =
  let server = Some( string(controller.["__server"]) )
  let ctrlPath = controller.["__Path"].ToString().Replace(@"\",@"\\")
  let query = sprintf "Select * From MsVM_ResourceAllocationSettingData Where PARENT='%s'" ctrlPath
  get_WmiObjects (getVirtualizationManagementPath server) query
  
(*
Address = null
AllocationUnits = Disks
AutomaticAllocation = True
AutomaticDeallocation = True
Caption = Hard Disk Image
Connection = System.String[] = [|"C:\\Users\\Public\\Documents\\Hyper-V\\Virtual Hard Disks\\Virtual Machine test1.vhd"|]
ConsumerVisibility = 3
Description = Settings for the Microsoft Hard Disk Image.
ElementName = Hard Disk Image
HostResource = null
InstanceID = Microsoft:AD9037F2-A459-4ABD-B692-D1153BC8522E\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\0\0\L
Limit = 1
MappingBehavior = null
OtherResourceType = null
Parent = \\MARANELLO\root\virtualization:Msvm_ResourceAllocationSettingData.InstanceID="Microsoft:AD9037F2-A459-4ABD-B692-D1153BC8522E\\83F8638B-8DCA-4152-9EDA-2CA8B33039B4\\0\\0\\D"
PoolID = Microsoft:70BB60D2-A9D3-46aa-B654-3DE53004B4F8\Root
Reservation = 1
ResourceSubType = Microsoft Virtual Hard Disk
ResourceType = 21
VirtualQuantity = 1
Weight = 0
*)  
let get_VMDiskByDrive (drive:ManagementObject) =
  let server = Some( string(drive.["__server"]) )
  let drivePath = drive.["__Path"].ToString().Replace(@"\",@"\\")
  let query = sprintf "Select * From MsVM_ResourceAllocationSettingData Where PARENT='%s'" drivePath
  get_WmiObject (getVirtualizationManagementPath server) query
  

let get_VMDisks vm =
  let list = new System.Collections.Generic.List<ManagementObject>()
  for cont in get_VMDiskControllers vm do        // foreach controller in...
    for drive in get_VMDrivesByController cont do        // foreach drive in...
      try
        let disk = get_VMDiskByDrive (drive :?> ManagementObject)
        list.Add(disk)
      with _ -> ()
  list.ToArray()


let add_VMSCSIController (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use scsirasd = new_VMRasd server 6 "Microsoft Synthetic SCSI Controller"
  scsirasd.["ElementName"] <- "VMBus SCSI Controller"
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| scsirasd.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to add resources. Error %d" code
  
    

let remove_VMSCSIcontroller (controller:ManagementObject) =
  // TODO: try to retrieve the virtual machine in a better way
  let vmName = get_VMNameByDiskController controller
  let server = Some( string(controller.["__server"]) )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope

  //# to Avoid leaving orphaned objects behind we're going to 
  //#             (1) Get the drives bound to the controller, 
  //              (2) remove the disks inserted into them, 
  //              (3) Then Remove the drives
  //# and finally (4) Remove the controller
  //# So we have to set up the arguments array and call the Remove method more than once. 

  for drive in get_VMDrivesByController controller do
    let drive = (drive :?> ManagementObject)
    let disk = get_VMDiskByDrive drive
    
    // http://msdn.microsoft.com/en-us/library/cc160711(VS.85).aspx
    use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
    inParams.["TargetSystem"] <- vm.Path.Path
    inParams.["ResourcesettingData"] <- [| disk.Path.Path |]    
    use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)
    
    use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
    inParams.["TargetSystem"] <- vm.Path.Path
    inParams.["ResourcesettingData"] <- [| drive.Path.Path |]
    use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)
    ()
    
  use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| controller.Path.Path |]

  use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to remove the controller. Error %d" code


let add_VMDVDDriveToController (controller:ManagementObject) (lun:int) =
  // TODO: try to retrieve the virtual machine in a better way
  let vmName = get_VMNameByDiskController controller
  let server = Some( string(controller.["__server"]) )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use diskRASD = new_VMRasd server 16 "Microsoft Synthetic DVD Drive"
  diskRASD.["Parent"] <- controller.Path.Path
  diskRASD.["Address"] <- lun
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(( outParams.["NewResources"] :?> string array ).[0] )
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(( outParams.["NewResources"] :?> string array ).[0] )
  else
    failwithf "Failed to add the drive. Error %d" code
  


let add_VMDiskDriveToController (controller:ManagementObject) (lun:int) =
  // TODO: try to retrieve the virtual machine in a better way
  let vmName = get_VMNameByDiskController controller
  let server = Some( string(controller.["__server"]) )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use diskRASD = new_VMRasd server 22 "Microsoft Synthetic Disk Drive"
  diskRASD.["Parent"] <- controller.Path.Path
  diskRASD.["Address"] <- lun
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(( outParams.["NewResources"] :?> string array ).[0] )
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(( outParams.["NewResources"] :?> string array ).[0] )
  else
    failwithf "Failed to add the drive. Error %d" code
  


//# Similar to Adding the drive, but we request a different resource type,
//  and the parent is the 'Microsoft Synthetic Disk Drive', instead of disk controller
//# Mount an ISO in a DVD drive or A VHD in a Disk drive 
let add_VMDVDDiskToController (controller:ManagementObject) (vhdPath:string) =
  // TODO: try to retrieve the virtual machine in a better way
  let vmName = get_VMNameByDiskController controller
  let server = Some( string(controller.["__server"]) )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use diskRASD = new_VMRasd server 21 "Microsoft Virtual CD/DVD Disk"
  diskRASD.["Parent"] <- controller.Path.Path
  diskRASD.["Connection"] <- vhdPath
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to add the drive. Error %d" code



//let add_VMHardDiskToController (controller:ManagementObject) (vhdPath:string) =
//  let vmName = get_VMNameByDiskController controller
//  let server = Some( string(controller.["__server"]) )
//  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
//  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
//  use vm = Utility.getTargetComputer vmName scope
//  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
//  use diskRASD = new_VMRasd server 21 "Microsoft Virtual Hard Disk"
//  use drive = (get_VMDriveByController controller |> moc_toArray).[0]
//  diskRASD.["Parent"] <- drive.Path.Path
//  diskRASD.["Connection"] <- [| vhdPath |]
//  inParams.["TargetSystem"] <- vm.Path.Path
//  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
//  
//  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
//  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)
//
//  let code = outParams.["ReturnValue"] :?> uint32
//  else if code = uint32(Utility.ReturnCode.Started) then
//    if Utility.jobCompleted outParams scope then true      
//    else failwithf "Failed to add the drive"
//  else if code = uint32(Utility.ReturnCode.Completed) then
//    true
//  else
//    failwithf "Failed to add the drive. Error %d" code


let add_VMHardDiskToDrive (drive:ManagementObject) (vhdPath:string) =
  // TODO: try to retrieve the virtual machine in a better way
  let controller = new ManagementObject(string drive.["Parent"])
  let vmName = get_VMNameByDiskController controller
  let server = string drive.["__server"]
  let scope = Utility.getManagementScope (getVirtualizationManagementPath (Some server))
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use vm = Utility.getTargetComputer vmName scope
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use diskRASD = new_VMRasd (Some server) 21 "Microsoft Virtual Hard Disk"
  diskRASD.["Parent"] <- drive.Path.Path
  diskRASD.["Connection"] <- [| vhdPath |]
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to add the drive. Error %d" code



let private set_VMDiskToDrive (vm:ManagementObject) (rasd:ManagementObject) (path:string) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  rasd.["Connection"] <- path
  use inParams = virtualSystemService.GetMethodParameters("ModifyVirtualSystemResources")
  inParams.["ComputerSystem"] <- vm.Path.Path 
  inParams.["ResourcesettingData"] <- [| rasd.GetText(TextFormat.WmiDtd20) |]

  // http://msdn.microsoft.com/en-us/library/cc136807(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ModifyVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to change the disk. Error %d" code



let remove_VMDrive (vm:ManagementObject) (disk:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| disk.Path.Path |]

  // http://msdn.microsoft.com/en-us/library/cc160711(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to remove the disk. Error %d" code
  
  

let add_VMFloppyDisk (vm:ManagementObject) (vfdPath:string) =
  if not(vfdPath.ToUpper().EndsWith("VFD")) then failwith "Invalid filename"
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"

  let query = sprintf "Select * From MsVM_ResourceAllocationSettingData Where instanceId Like 'Microsoft:%s%%' and resourceSubtype='Microsoft Synthetic Diskette Drive'" (string vm.["Name"])
  let res = get_WmiObject (getVirtualizationManagementPath server) query

  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use diskRASD = new_VMRasd server 21 "Microsoft Virtual Floppy Disk"
  diskRASD.["Parent"] <- res.Path.Path
  diskRASD.["Connection"] <- [| vfdPath |]
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| diskRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to add the floppy disk. Error %d" code



let get_VMFloppyDisk (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let param = string vm.["Name"]
  let query = sprintf "Select * From MsVM_ResourceAllocationSettingData Where instanceId Like 'Microsoft:%s%%' and resourceSubtype='Microsoft Virtual Floppy Disk'" param
  get_WmiObject (getVirtualizationManagementPath server) query



let remove_VMFloppyDisk (vm:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  let floppy = get_VMFloppyDisk vm

  use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| floppy.Path.Path |]

  // http://msdn.microsoft.com/en-us/library/cc160711(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to remove the floppy disk. Error %d" code




(* ------------------------------------------------------------------------------------------------------ *)
(* # Functions for working with Networking, (NICS, switches and ports on switches that nics connect to) # *)


/// <summary>
/// Gets all the Virtual Network for the specified HyperV server
/// </summary>
let get_AllVMSwitch server =
  let query = sprintf "Select * From MsVM_VirtualSwitch"
  get_WmiObjects (getVirtualizationManagementPath server) query |> moc_toArray

/// <summary>
/// Gets the specified Virtual Network in the specified HyperV server
/// </summary>
let get_VMSwitch server switchName =
  let query = sprintf "Select * From MsVM_VirtualSwitch Where ElementName='%s'" switchName
  get_WmiObject (getVirtualizationManagementPath server) query



/// <summary>
/// Gets all the Virtual Network Interface Cards for the specified Virtual Machine
/// TODO: test the switches, I don't know if they works correctly
/// <summary>
/// <param name="server">The HyperV server hostname</param>
/// <param name="vmName">The Virtual Machine name</param>
/// <param name="getLegacy">True to gets only Legacy adapters, False to get only non-legacy adapters</param>
/// <param name="getVmBus"></param>
let rec get_VMNic (vm:ManagementObject) getLegacy getVmBus =
  match (getLegacy,getVmBus) with
  | (false,false) -> failwith "Select at least one of legacy or vmBus"
  | (true,true) -> Array.append (get_VMNic vm false true) (get_VMNic vm true false)
  | (_,_) -> let server = Some( string vm.["__server"] )
             let vssd = get_VMSettingData vm
             let param = string vssd
             let query = if getLegacy then sprintf "Associators of {%s} where resultClass=MsVM_EmulatedEthernetPortSettingData" param
                         else sprintf "Associators of {%s} where resultClass=MsVM_SyntheticEthernetPortSettingData" param
             get_WmiObjects (getVirtualizationManagementPath server) query |> moc_toArray


/// <summary>
/// Gets all the Virtual Network Interface Cards for all Virtual Machines in the specified HyperV server
/// </summary>
/// <param name="server">The HyperV server hostname</param>
/// <param name="getLegacy">True to gets only Legacy adapters, False to get only non-legacy adapters</param>
/// <param name="getVmBus"></param>
let private get_AllVMNic server getLegacy getVmBus =
  let allVm = get_AllVM server
  let f v = try get_VMNic v getLegacy getVmBus with _ -> [||]
  [| for vm in allVm do yield! f vm |]
  

(*
BroadcastResetSupported = False
Caption = Switch Port
CreationClassName = Msvm_SwitchPort
Description = 
ElementName = eb323175-d03a-4d9a-822e-50f1b1cacfd5
EnabledDefault = 2
EnabledState = 5
HealthState = 5
InstallDate = null
Name = eb323175-d03a-4d9a-822e-50f1b1cacfd5
NameFormat = null
OperationalStatus = System.UInt16[]
OtherEnabledState = null
OtherTypeDescription = Virtual Ethernet
PortNumber = null
ProtocolIFType = 1
ProtocolType = null
RequestedState = 12
ScopeOfResidence = 
Status = null
StatusDescriptions = System.String[]
SystemCreationClassName = Msvm_VirtualSwitch
SystemName = f7c2cf5f-9c4a-4c12-8348-e38a45d385a8
TimeOfLastStateChange = null
*)
let private get_VMNicPort (nic:ManagementObject) =
  let server = string nic.["__server"]
  let param0 = (nic.["Connection"] :?> string array).[0].Replace(@"\",@"\\")
  let query = sprintf "Select * From Msvm_SwitchPort where __Path='%s'" param0
  get_WmiObject_or_null (getVirtualizationManagementPath (Some server)) query
  

(*
Caption = Virtual Switch
CreationClassName = Msvm_VirtualSwitch
Dedicated = null
Description = Microsoft Virtual Switch
ElementName = Private
EnabledDefault = 2
EnabledState = 5
HealthState = 5
IdentifyingDescriptions = null
InstallDate = null
MaxChimneyOffloads = 0
MaxVMQOffloads = 0
Name = 21b0c03b-446f-44db-a5d1-8121ee8672b5
NameFormat = null
NumLearnableAddresses = 100
OperationalStatus = System.UInt16[]
OtherDedicatedDescriptions = null
OtherEnabledState = null
OtherIdentifyingInfo = null
PowerManagementCapabilities = null
PrimaryOwnerContact = null
PrimaryOwnerName = NT AUTHORITY\SYSTEM
RequestedState = 12
ResetCapability = 5
Roles = null
ScopeOfResidence = 
Status = null
StatusDescriptions = System.String[]
TimeOfLastStateChange = null
*)
//
/// <summary>
/// Gets the Switch used by the specified NIC
/// </summary>
let get_VMNicSwitch (nic:ManagementObject) =
  let server = string nic.["__server"]
  let nicPort = get_VMNicPort nic
  if nicPort = null then null
  else
    let param = string nicPort
    let query = sprintf "ASSOCIATORS OF {%s} where resultclass=Msvm_VirtualSwitch" param
    get_WmiObject (getVirtualizationManagementPath (Some server)) query



let private get_VMSwitchPorts server =
  let query = "Select * From Msvm_SwitchPort"
  let p = get_WmiObjects (getVirtualizationManagementPath server) query
  moc_toArray p

let private get_VMSwitchPort server (guid:System.Guid) =
  let query = sprintf "Select * From Msvm_SwitchPort where Name=\"%s\"" (guid.ToString())
  get_WmiObject (getVirtualizationManagementPath server) query


// Not intended to be called directly, used by add_VMNIC and set_VMNICConnection
let private new_VMSwitchPort (virtualSwitch:ManagementObject) (portName:string) =
  let guid = System.Guid.NewGuid()
  let server = string virtualSwitch.["__server"]
  let scope = Utility.getManagementScope (getVirtualizationManagementPath (Some server))
  use switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
  use inParams = switchService.GetMethodParameters("CreateSwitchPort")
  inParams.["Name"] <- string guid
  inParams.["FriendlyName"] <- portName
  inParams.["VirtualSwitch"] <- virtualSwitch.Path.Path
  inParams.["ScopeofResidence"] <- null

  // http://msdn.microsoft.com/en-us/library/cc136782(VS.85).aspx
  use outParams = switchService.InvokeMethod("CreateSwitchPort", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(string outParams.["CreatedSwitchPort"])
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(string outParams.["CreatedSwitchPort"])
  else failwithf "Failed to create VirtualSwitchPort. Error %d" code



// TODO: Add check to validate the mac address!
/// <summary>
/// Adds a Virtual Network Interface Card in the specified Virtual Machine
/// </summary>
/// <param name="server">The HyperV server hostname</param>
/// <param name="vmName">The Virtual Machine name</param>
/// <param name="virtualSwitch">The Virtual Network setting used by this NIC; None to sets the NIC "Not Connected"</param>
/// <param name="legacy">True to create a legacy NIC; False otherwise</param>
/// <param name="macAddress">A MAC address statically assigned to the NIC; None to assign a dynamic MAC Address</param>
let add_VMNic (vm:ManagementObject) (virtualSwitch:ManagementObject option) legacy (macAddress:string option) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("AddVirtualSystemResources")
  use nicRASD =
    if legacy then
      let r = new_VMRasd server 10 "Microsoft Emulated Ethernet Port"
      r.["ElementName"] <- "Legacy Network Adapter"
      r
    else
      let guid = sprintf @"{%s}" (System.Guid.NewGuid().ToString())
      let r = new_VMRasd server 10 "Microsoft Synthetic Ethernet Port"
      r.["ElementName"] <- "Network Adapter"
      r.["VirtualSystemIdentifiers"] <- [| guid |]
      r
  match virtualSwitch with
  | None -> nicRASD.["Connection"] <- null
  | Some(sw) -> let newPort = new_VMSwitchPort sw "Network Adapter InternalPort"
                nicRASD.["Connection"] <- [| string newPort |]
  match macAddress with
  | None -> nicRASD.["StaticMacAddress"] <- false
  | Some(mac) -> nicRASD.["Address"] <- mac
                 nicRASD.["StaticMacAddress"] <- true
  
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| nicRASD.GetText(TextFormat.WmiDtd20) |]
  
  // http://msdn.microsoft.com/en-us/library/cc160705(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("AddVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to add the NIC. Error %d" code



/// <summary>
/// Sets a Virtual Network setting to the specified NIC in the specified Virtual Machine
/// </summary>
/// <param name="server">The HyperV server hostname</param>
/// <param name="vmName">The Virtual Machine name</param>
/// <param name="nic">The Virtual Network Interface Card</param>
/// <param name="virtualSwitch">The Virtual Newtork setting to use; None to set the NIC "Not Connected"</param>
let set_VMNicSwitch (vm:ManagementObject) (nic:ManagementObject) (virtualSwitch:ManagementObject option) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  
  use oldPort =
    let param0 = (nic.["Connection"] :?> string array).[0].Replace(@"\",@"\\")
    let query = sprintf "Select * From Msvm_SwitchPort where __Path='%s'" param0
    get_WmiObject_or_null (getVirtualizationManagementPath server) query
  
  if oldPort <> null then
    let switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
    use inParams = switchService.GetMethodParameters("DeleteSwitchPort")
    inParams.["SwitchPort"] <- oldPort.Path.Path
    // http://msdn.microsoft.com/en-us/library/cc136788.aspx
    use outParams = switchService.InvokeMethod("DeleteSwitchPort", inParams, null)
    let code = outParams.["ReturnValue"] :?> uint32
    if code <> uint32(Utility.ReturnCode.Completed) then failwith "Failed to remove switch port"

  let newPort =
    match virtualSwitch with
    | None -> ""
    | Some(x) -> string (new_VMSwitchPort x "NIC Switch InternalPort")
  
  nic.["Connection"] <- [| newPort |]

  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"

  let resources = [| nic.GetText(TextFormat.WmiDtd20) |]
  use inParams = virtualSystemService.GetMethodParameters("ModifyVirtualSystemResources")
  inParams.["ComputerSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- resources

  // http://msdn.microsoft.com/en-us/library/cc136807(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ModifyVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to change Network connection. Error: %d" code



// TODO: Add check to validate the mac address!
let set_VMNicAddress (vm:ManagementObject) (nic:ManagementObject) (macAddress:string) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"

  nic.["Address"] <- macAddress
  nic.["StaticMacAddress"] <- true

  let resources = [| nic.GetText(TextFormat.WmiDtd20) |]
  use inParams = virtualSystemService.GetMethodParameters("ModifyVirtualSystemResources")
  inParams.["ComputerSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- resources

  // http://msdn.microsoft.com/en-us/library/cc136807(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ModifyVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to Set Mac Address. Error: %d" code




let remove_VMNic (vm:ManagementObject) (nic:ManagementObject) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("RemoveVirtualSystemResources")
  inParams.["TargetSystem"] <- vm.Path.Path
  inParams.["ResourcesettingData"] <- [| nic.Path.Path |]

  // http://msdn.microsoft.com/en-us/library/cc160711(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("RemoveVirtualSystemResources", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to remove the NIC. Error %d" code
  


let get_VMNicByMACaddress server macAddress =
  let allVmNic = get_AllVMNic server true true
  allVmNic |> Array.find (fun x -> (string x.["Address"]) = macAddress)


let get_VMByMACaddress server macAddress =
  let nic = get_VMNicByMACaddress server macAddress
  let param = nic.["InstanceID"].ToString().Split([|'\\'|]).[0].Split([|':'|]).[1]
  let query = sprintf "Select * from msvm_computersystem where name='%s'" param
  get_WmiObject (getVirtualizationManagementPath server) query


let get_ExternalEthernets server =
  let query = sprintf "Select * from Msvm_ExternalEthernetPort"
  get_WmiObjects (getVirtualizationManagementPath server) query


let get_ExternalEthernet server (ethName:string) =
  let query = sprintf "Select * from Msvm_MountedStorageImage where Name='%s'" ethName
  get_WmiObject (getVirtualizationManagementPath server) query


let get_VMSwitch_from_externalEthernetDevice (externalEthernetDevice:ManagementObject) =
  let boh1 = externalEthernetDevice.GetRelated("Msvm_SwitchLANEndpoint", null, null, null, null, null, false, null) |> moc_toArray
  if boh1.Length > 0 then
    let boh2 = boh1.[0].GetRelated("Msvm_SwitchPort", null, null, null, null, null, false, null) |> moc_toArray
    if boh2.Length > 0 then
      let boh3 = boh2.[0].GetRelated("Msvm_VirtualSwitch", null, null, null, null, null, false, null) |> moc_toArray
      if boh3.Length > 0 then
        boh3.[0]
      else null
    else null
  else null


// 0 = private, 1 = internal, 2 = external
let get_VMSwitchType (vmSwitch:ManagementObject) =
  let cc = vmSwitch.GetRelated("Msvm_SwitchPort").Count
  match cc with
  | 0 -> 0      // privates has no switchports
  | 1 -> 1      // internals has one switchport
  | _ -> 2      // externals has three switchports


(* Note, I have not provided a delete function for this
 * if you want to write one look at the DeleteSwitch method VirtualSwitchManagementService
 *)
let new_VMPrivateSwitch server (virtualSwitchName:string) (learnableAddress:int) =
  let guid = System.Guid.NewGuid().ToString()
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
  use inParams = switchService.GetMethodParameters("CreateSwitch")
  inParams.["Name"] <- guid
  inParams.["FriendlyName"] <- virtualSwitchName
  inParams.["NumLearnableAddresses"] <- learnableAddress
  inParams.["ScopeofResidence"] <- null

  // http://msdn.microsoft.com/en-us/library/cc136783(VS.85).aspx
  use outParams = switchService.InvokeMethod("CreateSwitch", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(string outParams.["CreatedVirtualSwitch"])
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(string outParams.["CreatedVirtualSwitch"])
  else
    failwithf "Failed to create Virtual Switch. Error %d" code



let private CreateInternalEthernetPortDynamicMac server (virtualSwitchName:string) =
  let guid = System.Guid.NewGuid().ToString()
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
  use inParams = switchService.GetMethodParameters("CreateInternalEthernetPortDynamicMac")
  inParams.["Name"] <- guid
  inParams.["FriendlyName"] <- virtualSwitchName

  // http://msdn.microsoft.com/en-us/library/cc443602(v=vs.85).aspx
  use outParams = switchService.InvokeMethod("CreateInternalEthernetPortDynamicMac", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(string outParams.["CreatedInternalEthernetPort"])
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(string outParams.["CreatedInternalEthernetPort"])
  else
    failwithf "Failed to create a new internal Ethernet port. Error %d" code

let private ConnectSwitchPort server (switchPort:ManagementObject) (lanEndPoint:ManagementObject) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
  use inParams = switchService.GetMethodParameters("ConnectSwitchPort")
  inParams.["SwitchPort"] <- switchPort.Path.Path
  inParams.["LANEndpoint"] <- lanEndPoint.Path.Path

  // http://msdn.microsoft.com/en-us/library/cc136774(v=vs.85).aspx
  use outParams = switchService.InvokeMethod("ConnectSwitchPort", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    new ManagementObject(string outParams.["ActiveConnection"])
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    new ManagementObject(string outParams.["ActiveConnection"])
  else
    failwithf "Failed to connect the switch port to the LAN endpoint. Error %d" code

let new_VMInternalSwitch server virtualSwitchName learnableAddress =
  let privateSwitch = new_VMPrivateSwitch server virtualSwitchName learnableAddress
  let switchPort = new_VMSwitchPort privateSwitch (virtualSwitchName + "_InternalPort")
  let boh1 = CreateInternalEthernetPortDynamicMac server virtualSwitchName
  let endPoints = boh1.GetRelated("Msvm_SwitchLANEndpoint", null, null, null, "Dependent", "Antecedent", false, null)
  let endPoint = let a = moc_toArray endPoints in if a.Length > 0 then a.[0] else null
  let boh2 = ConnectSwitchPort server switchPort endPoint
  get_VMSwitch server virtualSwitchName



let private SetupSwitch server (externalSwitchPort:ManagementObject) (internalSwitchPort:ManagementObject) (externalEthernet:ManagementObject) (ethernetPortName:string) =
  let guid = System.Guid.NewGuid().ToString()
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use switchService = Utility.getServiceObject scope "Msvm_VirtualSwitchManagementService"
  use inParams = switchService.GetMethodParameters("SetupSwitch")
  inParams.["ExternalSwitchPort"] <- externalSwitchPort.Path.Path
  inParams.["InternalSwitchPort"] <- internalSwitchPort.Path.Path
  inParams.["ExternalEthernetPort"] <- string externalEthernet.["__Path"]
  inParams.["InternalEthernetPortName"] <- guid
  inParams.["InternalEthernetPortFriendlyName"] <- ethernetPortName

  // http://msdn.microsoft.com/en-us/library/cc136774(v=vs.85).aspx
  use outParams = switchService.InvokeMethod("SetupSwitch", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then
    ()
  else if code = uint32(Utility.ReturnCode.Started) then
    Utility.jobCompleted outParams scope
    ()
  else
    failwithf "Failed to setup the external switch. Error %d" code


let new_VMExternalSwitch server externalEthernetDevice virtualSwitchName learnableAddress =
  let privateSwitch = new_VMPrivateSwitch server virtualSwitchName learnableAddress
  let intSwitchPort = new_VMSwitchPort privateSwitch (virtualSwitchName + "_InternalPort")
  let extSwitchPort = new_VMSwitchPort privateSwitch (virtualSwitchName + "_ExternalPort")
  SetupSwitch server extSwitchPort intSwitchPort externalEthernetDevice virtualSwitchName
  get_VMSwitch server virtualSwitchName




(* -------------------------------------------------- *)
(* # Functions for managing Serial port connections # *)




(* ------------------------------------------------------------- *)
(* # Functions for managing VM State (Snapshots and VM Export) # *)


/// <summary>
/// Exports the specified Virtual Machine in the local HyperV server to the specified path.
/// The path where the VM will be exported can be a network resource.
/// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
/// </summary>
let export_VM (vm:ManagementObject) exportDirectory (copyVmState:bool) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("ExportVirtualSystem")
  inParams.["ComputerSystem"] <- vm.Path.Path
  inParams.["CopyVmState"] <- copyVmState
  if not( System.IO.Directory.Exists(exportDirectory) ) then
    System.IO.Directory.CreateDirectory(exportDirectory) |> ignore
  inParams.["ExportDirectory"] <- exportDirectory
  
  // http://msdn.microsoft.com/en-us/library/cc136793(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ExportVirtualSystem", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to export the virtual machine. Error %d" code



/// <summary>
/// Exports the specified Virtual Machine in the local HyperV server to the specified path.
/// The path where the VM will be exported can be a network resource.
/// NOTE: This method was introduced with Windows Server 2008 R2.
/// </summary>
let exportEx_VM (vm:ManagementObject) exportDirectory (copyVmStorage:bool) (copyVmRuntimeInformation:bool) =
  let server = Some( string vm.["__server"] )
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("ExportVirtualSystemEx")
  inParams.["ComputerSystem"] <- vm.Path.Path

  inParams.["ExportDirectory"] <- exportDirectory
  if not( System.IO.Directory.Exists(exportDirectory) ) then
    System.IO.Directory.CreateDirectory(exportDirectory) |> ignore
  
  let virtualSystemExportSettingDataInstance =
      let settingPath = new ManagementPath("Msvm_VirtualSystemExportSettingData")
      use exportSettingDataClass = new ManagementClass(scope, settingPath, null)
      use exportSettingData = exportSettingDataClass.CreateInstance()
      exportSettingData.["CopySnapshotConfiguration"] <- 0
      exportSettingData.["CopyVmRuntimeInformation"] <- copyVmRuntimeInformation
      exportSettingData.["CopyVmStorage"] <- copyVmStorage
      exportSettingData.["CreateVmExportSubdirectory"] <- false
      exportSettingData.GetText(TextFormat.CimDtd20)

  inParams.["ExportSettingData"] <- virtualSystemExportSettingDataInstance
  
  // http://msdn.microsoft.com/en-us/library/dd379580(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ExportVirtualSystemEx", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to export the virtual machine. Error %d" code
  


/// <summary>
/// Imports an exported Virtual Machine from the specified local path.
/// NOTE: Beginning with Windows Server 2008 R2 this method is deprecated.
/// </summary>
let import_VM server (importDirectory:string) (generateNewID:bool) =
  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("ImportVirtualSystem")
  inParams.["GenerateNewID"] <- string(generateNewID)
  inParams.["ImportDirectory"] <- importDirectory

  // http://msdn.microsoft.com/en-us/library/cc136798(VS.85).aspx
  use outParams = virtualSystemService.InvokeMethod("ImportVirtualSystem", inParams, null)

  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to import the virtual machine. Error %d" code


let private getVirtualSystemImportSettingData scope (importDirectory:string) (rootDirectoryToCopy:string) (newVmName:string) (generateNewID:bool) (createCopy:bool) =
  let virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  let inParams = virtualSystemService.GetMethodParameters("GetVirtualSystemImportSettingData")
  inParams.["ImportDirectory"] <- importDirectory
  
  // http://msdn.microsoft.com/en-us/library/dd379581(VS.85).aspx
  // http://msdn.microsoft.com/en-us/library/dd379583(VS.85).aspx (ImportVirtualSystemEx, used there too) 
  let outParams = virtualSystemService.InvokeMethod("GetVirtualSystemImportSettingData", inParams, null)
  
  let importSettingData =
      let code = outParams.["ReturnValue"] :?> uint32
      if code = uint32(Utility.ReturnCode.Completed) then
        outParams.["ImportSettingData"] :?> ManagementBaseObject
      else if code = uint32(Utility.ReturnCode.Started) then
        Utility.jobCompleted outParams scope
        outParams.["ImportSettingData"] :?> ManagementBaseObject
      else
        failwithf "Failed to get the Import Setting Data. Error %d" code

  importSettingData.["GenerateNewId"] <- generateNewID
  importSettingData.["CreateCopy"] <- createCopy
  importSettingData.["Name"] <- newVmName

  if createCopy then
    importSettingData.["TargetVmDataRoot"] <- rootDirectoryToCopy
    importSettingData.["TargetSnapshotDataRoot"] <- rootDirectoryToCopy
    importSettingData.["TargetVhdDataRoot"] <- rootDirectoryToCopy
  
  importSettingData.["SourceResourcePaths"] <- importSettingData.["CurrentResourcePaths"]
  importSettingData



/// <summary>
/// Imports an exported Virtual Machine from the specified local path.
/// NOTE: This method was introduced with Windows Server 2008 R2.
/// </summary>
let importEx_VM server (newVmName:string) (generateNewID:bool) (importDirectory:string) (createCopy:bool) (rootDirectoryToCopy:string)  =
  if createCopy then
      if not(System.IO.Directory.Exists(rootDirectoryToCopy)) then
        System.IO.Directory.CreateDirectory(rootDirectoryToCopy) |> ignore
      //else
      //  failwith "The destination directory already exists"

  let scope = Utility.getManagementScope (getVirtualizationManagementPath server)
  use virtualSystemService = Utility.getServiceObject scope "Msvm_VirtualSystemManagementService"
  use inParams = virtualSystemService.GetMethodParameters("ImportVirtualSystemEx")

  let importSettingData =
    let tmp = getVirtualSystemImportSettingData scope importDirectory rootDirectoryToCopy newVmName generateNewID createCopy
    tmp.GetText(TextFormat.CimDtd20)

  inParams.["ImportDirectory"] <- importDirectory
  inParams.["ImportSettingData"] <- importSettingData
  
  // http://msdn.microsoft.com/en-us/library/dd379583(VS.85).aspx
  let outParams = virtualSystemService.InvokeMethod("ImportVirtualSystemEx", inParams, null)
  
  let code = outParams.["ReturnValue"] :?> uint32
  if code = uint32(Utility.ReturnCode.Completed) then ()
  else if code = uint32(Utility.ReturnCode.Started) then Utility.jobCompleted outParams scope
  else failwithf "Failed to import the virtual machine. Error %d" code


//
// ----------------------------------------------------------------------
// Other WMI queries, not related with HyperV
//

// http://msdn.microsoft.com/en-us/library/aa394435(VS.85).aspx
let resolveUncPath path =
  let m = System.Text.RegularExpressions.Regex.Match(path, @"^\\\\(?<server>\w+)\\(?<share>\w+)\\(.*)")
  let server = m.Result("${server}")
  let share = m.Result("${share}")  
  let filePath = m.Result("$1")

  let query = sprintf @"SELECT * FROM Win32_Share WHERE Name=""%s""" share
  let o = get_WmiObject_or_null (getManagementPath @"root\CIMV2" (Some server)) query

  if o = null then (null, null)
  else
    let localPath = sprintf @"%s\%s" (string o.["Path"]) filePath
    (server, localPath)
  

let ping_Hostname server fullyQualifiedName =
  let query = sprintf "select * from Win32_PingStatus where Address='%s' and ResolveAddressNames=True and recordRoute=1" fullyQualifiedName
  get_WmiObject (getManagementPath @"root\CIMV2" server) query
